#include "Precompiled.h"
#include "Bootstrapper.h"

#include "GameEngine/GameEngine.h"
#include "GraphicsPipeline/GraphicsPipeline9.h"
#include "GraphicsPipeline/GraphicsPipeline11.h"

namespace Game {

Bootstrapper::Bootstrapper() {
    this->graphicsPipeline = NULL;
}

Bootstrapper::~Bootstrapper() {
}

IGameEngine* Bootstrapper::GetGameEngine() const {
    return this->gameEngine;
}

void Bootstrapper::SetGameEngine(IGameEngine* val) {
    this->gameEngine = val;
}

int Bootstrapper::Run() {
    if (this->gameEngine == NULL) {
        return 0;
    }

    this->Initialize();

    // Only require 10-level hardware
    DXUTCreateDevice(D3D_FEATURE_LEVEL_10_0, true, 800, 600);

    DXUTMainLoop(); // Enter into the DXUT render loop

    this->Finalize();

    return DXUTGetExitCode();
}

void Bootstrapper::DeleteGraphicsPipeline() {
    this->graphicsPipeline->Finalize();
    SAFE_DELETE(this->graphicsPipeline);
}

void Bootstrapper::Finalize() {
    this->gameEngine->Finalize();

    DXUTSetCallbackDeviceChanging(NULL);
    DXUTSetCallbackDeviceRemoved(NULL);

    // Set the D3D9 DXUT callbacks. Remove these sets if the app doesn't need to support D3D9
    DXUTSetCallbackD3D9DeviceAcceptable(NULL);
    DXUTSetCallbackD3D9DeviceCreated(NULL);
    DXUTSetCallbackD3D9DeviceDestroyed(NULL);

    // Set the D3D11 DXUT callbacks. Remove these sets if the app doesn't need to support D3D11
    DXUTSetCallbackD3D11DeviceAcceptable(NULL);
    DXUTSetCallbackD3D11DeviceCreated(NULL);
    DXUTSetCallbackD3D11DeviceDestroyed(NULL);

    DXUTSetCallbackFrameMove(NULL);

    // Perform any application-level cleanup here
    this->DeleteGraphicsPipeline();
}

void Bootstrapper::Initialize() {
    this->gameEngine->Initialize();

    // DXUT will create and use the best device (either D3D9 or D3D11)
    // that is available on the system depending on which D3D callbacks are set below

    // Set general DXUT callbacks
    DXUTSetCallbackDeviceChanging(Bootstrapper::DeviceChangingCallback, this);
    DXUTSetCallbackDeviceRemoved(Bootstrapper::DeviceRemovedCallback, this);

    // Set the D3D9 DXUT callbacks. Remove these sets if the app doesn't need to support D3D9
    DXUTSetCallbackD3D9DeviceAcceptable(Bootstrapper::D3D9DeviceAcceptableCallback, this);
    DXUTSetCallbackD3D9DeviceCreated(Bootstrapper::D3D9DeviceCreatedCallback, this);
    DXUTSetCallbackD3D9DeviceDestroyed(Bootstrapper::D3D9DeviceDestroyedCallback, this);

    // Set the D3D11 DXUT callbacks. Remove these sets if the app doesn't need to support D3D11
    DXUTSetCallbackD3D11DeviceAcceptable(Bootstrapper::D3D11DeviceAcceptableCallback, this);
    DXUTSetCallbackD3D11DeviceCreated(Bootstrapper::D3D11DeviceCreatedCallback, this);
    DXUTSetCallbackD3D11DeviceDestroyed(Bootstrapper::D3D11DeviceDestroyedCallback, this);

    // Perform any application-level initialization here
    DXUTInit(true, true, NULL); // Parse the command line, show msgboxes on error, no extra command line params
    DXUTSetCursorSettings(true, true); // Show the cursor and clip it when in full screen
    DXUTCreateWindow(L"EmptyProject11");

    DXUTSetCallbackFrameMove(IGameEngine::FrameMoveCallback, this->gameEngine);
}

bool Bootstrapper::IsD3D9DeviceAcceptable(D3DCAPS9* pCaps, D3DFORMAT AdapterFormat, D3DFORMAT BackBufferFormat, bool bWindowed) {
    // Typically want to skip back buffer formats that don't support alpha blending
    IDirect3D9* pD3D = DXUTGetD3D9Object();
    if (FAILED(pD3D->CheckDeviceFormat(pCaps->AdapterOrdinal, pCaps->DeviceType,
                                       AdapterFormat, D3DUSAGE_QUERY_POSTPIXELSHADER_BLENDING,
                                       D3DRTYPE_TEXTURE, BackBufferFormat) ) ) {
        return false;
    }

    return true;
}

HRESULT Bootstrapper::D3D9DeviceCreated(IDirect3DDevice9* pd3dDevice, const D3DSURFACE_DESC* pBackBufferSurfaceDesc) {
    this->graphicsPipeline = new GraphicsPipeline::GraphicsPipeline9(pd3dDevice);
    this->graphicsPipeline->SetSceneManager(this->gameEngine->GetSceneManager());
    this->graphicsPipeline->Initialize();
    return S_OK;
}

void Bootstrapper::D3D9DeviceDestroyed() {
    this->DeleteGraphicsPipeline();
}

bool Bootstrapper::IsD3D11DeviceAcceptable(const CD3D11EnumAdapterInfo *AdapterInfo, UINT Output, const CD3D11EnumDeviceInfo *DeviceInfo, DXGI_FORMAT BackBufferFormat, bool bWindowed) {
    return true;
}

HRESULT Bootstrapper::D3D11DeviceCreated(ID3D11Device* pd3dDevice, const DXGI_SURFACE_DESC* pBackBufferSurfaceDesc) {
    this->graphicsPipeline = new GraphicsPipeline::GraphicsPipeline11(pd3dDevice);
    this->graphicsPipeline->SetSceneManager(this->gameEngine->GetSceneManager());
    this->graphicsPipeline->Initialize();
    return S_OK;
}

void Bootstrapper::D3D11DeviceDestroyed() {
    this->DeleteGraphicsPipeline();
}

bool Bootstrapper::OnDeviceRemoved() {
    return true;
}

bool Bootstrapper::OnDeviceChanging(DXUTDeviceSettings* pDeviceSettings) {
    return true;
}

bool CALLBACK Bootstrapper::D3D9DeviceAcceptableCallback(D3DCAPS9* pCaps, D3DFORMAT AdapterFormat, D3DFORMAT BackBufferFormat, bool bWindowed, void* pUserContext) {
    return static_cast<Bootstrapper*>(pUserContext)->IsD3D9DeviceAcceptable(pCaps, AdapterFormat, BackBufferFormat, bWindowed);
}

HRESULT CALLBACK Bootstrapper::D3D9DeviceCreatedCallback(IDirect3DDevice9* pd3dDevice, const D3DSURFACE_DESC* pBackBufferSurfaceDesc, void* pUserContext) {
    return static_cast<Bootstrapper*>(pUserContext)->D3D9DeviceCreated(pd3dDevice, pBackBufferSurfaceDesc);
}

void CALLBACK Bootstrapper::D3D9DeviceDestroyedCallback(void* pUserContext) {
    return static_cast<Bootstrapper*>(pUserContext)->D3D9DeviceDestroyed();
}

bool CALLBACK Bootstrapper::D3D11DeviceAcceptableCallback(const CD3D11EnumAdapterInfo *AdapterInfo, UINT Output, const CD3D11EnumDeviceInfo *DeviceInfo,
        DXGI_FORMAT BackBufferFormat, bool bWindowed, void* pUserContext) {
    return static_cast<Bootstrapper*>(pUserContext)->IsD3D11DeviceAcceptable(AdapterInfo, Output, DeviceInfo, BackBufferFormat, bWindowed);
}

HRESULT CALLBACK Bootstrapper::D3D11DeviceCreatedCallback(ID3D11Device* pd3dDevice, const DXGI_SURFACE_DESC* pBackBufferSurfaceDesc, void* pUserContext) {
    return static_cast<Bootstrapper*>(pUserContext)->D3D11DeviceCreated(pd3dDevice, pBackBufferSurfaceDesc);
}

void CALLBACK Bootstrapper::D3D11DeviceDestroyedCallback(void* pUserContext) {
    return static_cast<Bootstrapper*>(pUserContext)->D3D11DeviceDestroyed();
}

bool CALLBACK Bootstrapper::DeviceRemovedCallback(void* pUserContext) {
    return static_cast<Bootstrapper*>(pUserContext)->OnDeviceRemoved();
}

bool Bootstrapper::DeviceChangingCallback(DXUTDeviceSettings* pDeviceSettings, void* pUserContext) {
    return static_cast<Bootstrapper*>(pUserContext)->OnDeviceChanging(pDeviceSettings);
}

}